package main;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.TreeMap;

import business.algorithm.decisionAlgorithm.MovingAverage;
import business.algorithm.decisionAlgorithm.Order;
import business.algorithm.decisionAlgorithm.OutputForDecisionAlgorithm;
import dataAccess.databaseManagement.entity.AssetEntity;
import dataAccess.databaseManagement.entity.ExchangeEntity;
import dataAccess.databaseManagement.entity.PriceEntity;
import dataAccess.databaseManagement.manager.AssetManager;
import dataAccess.databaseManagement.manager.ExchangeManager;
import dataAccess.databaseManagement.manager.PriceManager;

public class TestReturn {
	public static ExchangeManager exchangeManager = new ExchangeManager();
	public static AssetManager assetManager = new AssetManager();
	public static PriceManager priceManager = new PriceManager();
	public static TreeMap<AssetEntity, ArrayList<PriceEntity>> database;
	public static ArrayList<PriceEntity> vnIndex;
	public static AssetEntity vnIndexAsset;
	
	public static double countTrading = 0;
	public static double truePredict = 0;


	/**
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		//load data from database
		prepareDatabase();
		
		//Choose interval
		int startIndex = findIndex(2012, 8, 30);
		int endIndex = findIndex(2012, 8, 30);
		
		//Choose list of assets
		ArrayList<AssetEntity> assetEntities = chooseAssetList();		
		
		//Start State
		double startVNIndexPoint = vnIndex.get(vnIndex.size() + startIndex - 1).getClose();
		double capital = 50000;
		double[] budget = { 0, capital, 0, 0, 0 };
		ArrayList<TreeMap<AssetEntity, Integer>> portfolios = new ArrayList<TreeMap<AssetEntity, Integer>>();
		for (int i = 0; i < 5; ++i) {
			portfolios.add(new TreeMap<AssetEntity, Integer>());
		}

		//Write trading logs to file
		BufferedWriter bufferedWriterResult = new BufferedWriter(new FileWriter(new File("returnResult.csv")));
		bufferedWriterResult.write("Date,TotalAsset,Return,VNINDEX_Point,VNINDEX_Return");
		bufferedWriterResult.newLine();
		
		String strOut = vnIndex.get(vnIndex.size() + startIndex - 1).getDate().toString() + ","
				+ capital
				+ ",0,"
				+ vnIndex.get(vnIndex.size() + startIndex - 1).getClose() + ","
				+ (vnIndex.get(vnIndex.size() + startIndex - 1).getClose() / startVNIndexPoint - 1);
		
		bufferedWriterResult.write(strOut);
		bufferedWriterResult.newLine();
		System.out.println(strOut);

		//Set value for parameters of auto-trading system
		double tradingFee = 0.003; //trading fee of system

		for (int n = startIndex; n < endIndex; n++) {
			Date curDate = vnIndex.get(vnIndex.size() + n).getDate();

			// next state before trading
			move(budget, portfolios);

			//buy new tickets
			if (budget[0] > 0) {				
				int noBoughtTickets = 5; //number of assets that we buy each date
				double cashForBuying = 0.2; //percentage of cash we use each date
				int interval = 20;
				int noSample = 20;
				boolean useMA = true;
				
				int maPeriods1 = 20;
				ArrayList<PriceEntity> priceEntities = new ArrayList<PriceEntity>();
				for (int i = Math.max(vnIndex.size() + n - maPeriods1*2, 0); i < vnIndex.size() + n; ++i) {
					priceEntities.add(vnIndex.get(i));
				}

				TreeMap<AssetEntity, ArrayList<PriceEntity>> map = new TreeMap<AssetEntity, ArrayList<PriceEntity>>();
				map.put(vnIndexAsset, priceEntities);

				MovingAverage movingAverage = new MovingAverage();
				movingAverage.setMA_period(maPeriods1);
				movingAverage.setPriceList(map);

				OutputForDecisionAlgorithm outputDA = movingAverage
						.runAlgorithm();

				ArrayList<Order> orderList = outputDA.getOrderList();

				if (orderList.size() > 0 && orderList.get(orderList.size() - 1).isOrderType() == Order.ORDER_BUY) {
//					int i = 0;
//					while (vnIndex.get(vnIndex.size()+n-1-i).getDate().after(orderList.get(orderList.size()-1).getDate())) {
//						i++;
//					}
					
					noBoughtTickets = 5;
					cashForBuying = 1;
					interval = 10;
					noSample = 1;
					useMA = false;					
				}
				
				TreeMap<Double, AssetEntity> output = findAssetToBuy(assetEntities, n, curDate, interval, noSample, useMA);
				buynewtickets(output, portfolios, budget, tradingFee, n, noBoughtTickets, cashForBuying);
			}			
			
			// sell old ticket
			sellDeclinePriceTickets(budget, portfolios, tradingFee, n);

			//Write trading logs to file
			double totalAsset = totalAsset(budget, portfolios, n);
			strOut = curDate.toString() + ","
					+ totalAsset + ","
					+ (totalAsset / capital - 1) + ","
					+ vnIndex.get(vnIndex.size() + n).getClose() + ","
					+ (vnIndex.get(vnIndex.size() + n).getClose()/ startVNIndexPoint - 1);
			
			System.out.println(strOut);
			bufferedWriterResult.write(strOut);
			bufferedWriterResult.newLine();
			bufferedWriterResult.flush();

			BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(new File(curDate.toString() + ".csv")));

			bufferedWriter.write("TotalAsset," + totalAsset);
			bufferedWriter.newLine();
			
			bufferedWriter.write("Budget," + budget[0] + "," + budget[1] + "," + budget[2] + "," + budget[3] + "," + budget[4]);
			bufferedWriter.newLine();
			
			int count = 0;
			for (TreeMap<AssetEntity, Integer> portfolio : portfolios) {
				bufferedWriter.write("Portfolio" + count);
				bufferedWriter.newLine();
				
				for (AssetEntity assetEntity : portfolio.keySet()) {
					ArrayList<PriceEntity> priceEntityList = database.get(assetEntity);
					
					double price = priceEntityList.get(priceEntityList.size() + n).getClose();
					bufferedWriter.write("," + assetEntity.getSymbol() + "," + portfolio.get(assetEntity) + "," + price);
					bufferedWriter.newLine();
				}
				
				count++;
			}

			bufferedWriter.close();
		}
		
		System.out.println(truePredict/countTrading);
		bufferedWriterResult.write(truePredict/countTrading + "");
		bufferedWriterResult.newLine();
		
		bufferedWriterResult.close();
	}

	private static void sellDeclinePriceTickets(double[] budget, ArrayList<TreeMap<AssetEntity, Integer>> portfolios, double tradingFee, int n) {
		ArrayList<AssetEntity> sellAssets = new ArrayList<AssetEntity>();
		TreeMap<AssetEntity, Integer> sellAssetsRemain = new TreeMap<AssetEntity, Integer>();
		
		for (AssetEntity assetEntity : portfolios.get(0).keySet()) {
			ArrayList<PriceEntity> priceEntityList = database.get(assetEntity);

			if (priceEntityList.get(priceEntityList.size() + n).getClose() < priceEntityList.get(priceEntityList.size() + n - 1).getClose()) {
				int noShare = (int) Math.min(
						portfolios.get(0).get(assetEntity), priceEntityList
								.get(priceEntityList.size() + n)
								.getVolume() / 10);
//				 if (priceEntityList.get(priceEntityList.size() +
//				 n).getLow() == priceEntityList.get(priceEntityList.size()
//				 + n).getHigh()) {
//				 noShare = 0;
//				 }
				double price = priceEntityList.get(priceEntityList.size() + n).getClose();
				budget[3] += price * noShare * (1 - tradingFee);
				sellAssets.add(assetEntity);
				if (noShare < portfolios.get(0).get(assetEntity)) {
					sellAssetsRemain.put(assetEntity, portfolios.get(0)
							.get(assetEntity) - noShare);
				}
			}
		}

		for (AssetEntity assetEntity : sellAssets) {
			portfolios.get(0).remove(assetEntity);
		}

		portfolios.get(0).putAll(sellAssetsRemain);
	}

	private static TreeMap<Double, AssetEntity> findAssetToBuy(ArrayList<AssetEntity> assetEntities, int n, Date curDate, int interval, int noSample, boolean useMA) throws IOException {				
		if (useMA) {
			int maPeriods = 5;
			
			ArrayList<PriceEntity> priceEntities = new ArrayList<PriceEntity>();
			for (int i = Math.max(vnIndex.size() + n - maPeriods*2, 0); i < vnIndex
					.size() + n; ++i) {
				priceEntities.add(vnIndex.get(i));
			}
	
			TreeMap<AssetEntity, ArrayList<PriceEntity>> map = new TreeMap<AssetEntity, ArrayList<PriceEntity>>();
			map.put(vnIndexAsset, priceEntities);
	
			MovingAverage movingAverage = new MovingAverage();
			movingAverage.setMA_period(maPeriods);
			movingAverage.setPriceList(map);
	
			OutputForDecisionAlgorithm outputDA = movingAverage
					.runAlgorithm();
	
			ArrayList<Order> orderList = outputDA.getOrderList();
	
			if (orderList.size() > 0 && orderList.get(orderList.size() - 1).isOrderType() == Order.ORDER_SELL) {
				return new TreeMap<Double, AssetEntity>();
			}
		}
		
		int noPredictedDates = 5;
		int noExpandedDates = 0;
		
		TreeMap<Double, AssetEntity> output = new TreeMap<Double, AssetEntity>(Collections.reverseOrder());

		for (AssetEntity assetEntity : assetEntities) {
			System.out.print(".");
			ArrayList<PriceEntity> priceEntityList = database.get(assetEntity);

			if (priceEntityList.size() + n < 100
					|| priceEntityList.get(priceEntityList.size() + n).getDate().getTime() != curDate.getTime()) {
				continue;
			}

			if (priceEntityList.get(priceEntityList.size() + n - 1).getLow() > priceEntityList.get(priceEntityList.size() + n - 2).getHigh() && 
					priceEntityList.get(priceEntityList.size() + n - 1).getVolume() > (vnIndex.get(vnIndex.size() + n - 1).getVolume() / 1000)
					&& priceEntityList.get(priceEntityList.size() + n - 1).getOpen() <= priceEntityList.get(priceEntityList.size() + n - 1).getClose()
					) {
				double[][] result;
				
//				if (!useMA) {
//					ArrayList<PriceEntity> priceEntities = new ArrayList<PriceEntity>();
//					for (int i = Math.max(priceEntityList.size() + n - 100,0); i < priceEntityList
//							.size() + n; ++i) {
//						priceEntities.add(priceEntityList.get(i));
//					}
//					result = predictUsingMatching(priceEntities.size(), priceEntities, interval, noSample, noPredictedDates, noExpandedDates);
//				} else {
//					result = predictUsingMatching(priceEntityList.size()+n, priceEntityList, interval, noSample, noPredictedDates, noExpandedDates);
//				}

				result = predictUsingMatching(priceEntityList.size()+n, priceEntityList, interval, noSample, noPredictedDates, noExpandedDates);
				double roc = new Double( result[0][result[0].length - 1] / priceEntityList.get(priceEntityList.size() + n - 1).getClose() - 1);

				if (roc > 0.05) {
					output.put(roc, assetEntity);
				}

			}
		}
		System.out.println();
		
		return output;
	}

	private static void move(double[] budget,
			ArrayList<TreeMap<AssetEntity, Integer>> portfolios) {
		for (int i = 0; i < 4; ++i) {
			budget[i] += budget[i + 1];
			budget[i + 1] = 0;
			for (AssetEntity assetEntity : portfolios.get(i + 1).keySet()) {
				if (portfolios.get(i).get(assetEntity) != null) {
					portfolios.get(i).put(
							assetEntity,
							portfolios.get(i + 1).get(assetEntity)
									+ portfolios.get(i).get(assetEntity));
				} else {
					portfolios.get(i).put(assetEntity, portfolios.get(i + 1).get(assetEntity));
				}
			}
			portfolios.get(i + 1).clear();
		}
	}

	private static ArrayList<AssetEntity> chooseAssetList() {
		String[] assetsList = {};
		
		ArrayList<AssetEntity> assetEntities = new ArrayList<AssetEntity>();
		if (assetsList.length == 0) {
			for (AssetEntity assetEntity : database.keySet()) {
				ArrayList<PriceEntity> priceEntityList = database.get(assetEntity);
				boolean flag = false;
				
				for (int i = 1; i < priceEntityList.size(); ++i) {
					double rate = priceEntityList.get(i).getClose()/ priceEntityList.get(i - 1).getClose() - 1;
					if ((rate < -0.06 || rate > 0.06)) {
						flag = true;
						break;
					}
				}
				
				if (flag) {
					continue;
				}
				assetEntities.add(assetEntity);
			}
		} else {
			for (AssetEntity assetEntity : database.keySet()) {
				for (String symbol : assetsList) {
					if (assetEntity.getSymbol().equals(symbol)) {
						assetEntities.add(assetEntity);
						break;
					}
				}
			}
		}
		
		return assetEntities;
	}

	private static int findIndex(int year, int month, int day) {
		Calendar calendar = Calendar.getInstance();
		
		calendar.set(year, month-1, day);
		Date date = new Date(calendar.getTime().getTime());

		int i = 1;
		while (i < vnIndex.size() && vnIndex.get(vnIndex.size() - i).getDate().after(date)) {
			i++;
		}
		return -i+1;
	}

	private static void buynewtickets(TreeMap<Double, AssetEntity> output,
			ArrayList<TreeMap<AssetEntity, Integer>> portfolios, double[] budget, double tradingFee, int n, int noBoughtTickets, double cashForBuying) {
		//noBoughtTickets : number of assets that we buy each date
		//cashForBuying : percentage of cash we use each date
		
		if (!output.keySet().isEmpty()) {
			double moneyUse = budget[0] - Math.max(0, budget[0] - sum(budget)*cashForBuying);
			double moneyUseForEachTickets = moneyUse/noBoughtTickets;
			
			budget[0] -= moneyUse;

			int count = 0;
			for (Double increasingRate : output.keySet()) {
				if (count >= noBoughtTickets) {
					break;
				}
				
				ArrayList<PriceEntity> priceEntityList = database.get(output.get(increasingRate));
				double price = priceEntityList.get(priceEntityList.size()+n).getClose();
				
				
				double money = moneyUseForEachTickets / (1 + tradingFee);
				int noShare = (int) (money / price);
				noShare = (noShare / 10) * 10;
				noShare = (int) Math.min(priceEntityList.get(priceEntityList.size() + n).getVolume() / 10, noShare);
				
//				 if (priceEntityList.get(priceEntityList.size() +
//				 n).getLow() ==
//				 priceEntityList.get(priceEntityList.size() +
//				 n).getHigh()) {
//				 noShare = 0;
//				 }
				
				if (noShare != 0) {
					System.out.println(output.get(increasingRate).getSymbol() + "," + increasingRate);
					moneyUse -= noShare * price * (1 + tradingFee);
					portfolios.get(4).put(output.get(increasingRate), noShare);
					countTrading++;
					if (n + 4 < 0 && (priceEntityList.get(priceEntityList.size()+n).getClose() < priceEntityList.get(priceEntityList.size() + n + 4).getClose())) {
						truePredict++;
					}
				}
				count++;
			}

			budget[0] += moneyUse;
		}

	}

	public static double totalAsset(double[] budget,
			ArrayList<TreeMap<AssetEntity, Integer>> portfolios, int n) {
		double total = sum(budget);
		for (TreeMap<AssetEntity, Integer> portfolio : portfolios) {
			for (AssetEntity assetEntity : portfolio.keySet()) {
				ArrayList<PriceEntity> priceEntityList = database
						.get(assetEntity);
				double assetPrice = priceEntityList.get(
						priceEntityList.size() + n).getClose();
				total += assetPrice * portfolio.get(assetEntity);
			}
		}
		return total;
	}

	public static double sum(double[] x) {
		double sum = 0;
		for (double e : x) {
			sum += e;
		}

		return sum;
	}

	public static double[][] predictUsingMatching(int startDateIndex,
			ArrayList<PriceEntity> priceEntities, int interval, int noSample, int noPredictedDates,
			int noExpandedDates) throws IOException {
//		int[] intervals = {5, 10, 20, 40};
//		int[] noSamples = {1, 5, 10};
//		
//		int noCrossValidation = 10;
//		int theBestInterval = 40;
//		int theBestNoSample = 1;
//		double minsError = Double.MAX_VALUE; 
		
		MatchingAlgorithm matchingAlgorithm = new MatchingAlgorithm();
		matchingAlgorithm.setPriceEntities(priceEntities);
		
//		for (int iInterval = 0; iInterval < intervals.length; ++iInterval ) {
//			for (int iNoSample = 0; iNoSample < noSamples.length; ++iNoSample ) {
//				matchingAlgorithm.setInterval(intervals[iInterval]);
//				matchingAlgorithm.setNoSample(noSamples[iNoSample]);
//				
//				double error = 0;
//				for (int i = 0; i < noCrossValidation; ++i) {					
//					double[][] result = matchingAlgorithm.predict(startDateIndex - noPredictedDates - i, noPredictedDates, noExpandedDates);
//					
//					error += Math.abs(result[0][result[0].length-1] - priceEntities.get(startDateIndex-i-1).getClose());
//				}
//				
//				if (error < minsError) {
//					minsError = error;
//					theBestInterval = intervals[iInterval];
//					theBestNoSample = noSamples[iNoSample];
//				}
//			}			
//		}
//
//		matchingAlgorithm.setInterval(theBestInterval);
//		matchingAlgorithm.setNoSample(theBestNoSample);

		matchingAlgorithm.setInterval(interval);
		matchingAlgorithm.setNoSample(noSample);
		
		return matchingAlgorithm.predict(startDateIndex, noPredictedDates,
				noExpandedDates);
	}

	@SuppressWarnings("unchecked")
	public static void prepareDatabase() throws IOException {
		File databaseFile = new File("database.dat");

		if (!databaseFile.exists()) {

			ExchangeEntity exchangeEntity = exchangeManager
					.getExchangeByName("HOSE");
			ArrayList<AssetEntity> assetEntities = assetManager
					.getAssetsByExchange(exchangeEntity.getExchangeID());

			vnIndexAsset = assetManager.getAssetBySymbolAndExchange("^VNINDEX",
					"INDEX");

			vnIndex = priceManager.getPriceByAssetID(vnIndexAsset.getAssetID());
			int vnIndexsize = vnIndex.size();

			database = new TreeMap<AssetEntity, ArrayList<PriceEntity>>();

			for (AssetEntity assetEntity : assetEntities) {
				ArrayList<PriceEntity> priceEntityList = priceManager
						.getPriceByAssetID(assetEntity.getAssetID());

				if (priceEntityList.size() == 0
						|| priceEntityList.get(priceEntityList.size() - 1)
								.getDate().getTime() != vnIndex
								.get(vnIndexsize - 1).getDate().getTime())
					continue;

				database.put(assetEntity, priceEntityList);

				System.out.println(assetEntity.getSymbol());
			}

			FileOutputStream fileOutput = null;
			ObjectOutputStream objectOutput = null;
			try {
				fileOutput = new FileOutputStream(databaseFile);
				objectOutput = new ObjectOutputStream(fileOutput);
				objectOutput.writeObject(database);
				objectOutput.writeObject(vnIndexAsset);
				objectOutput.writeObject(vnIndex);
				objectOutput.close();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			FileInputStream fileInput = null;
			ObjectInputStream objectInput = null;
			try {
				fileInput = new FileInputStream(databaseFile);
				objectInput = new ObjectInputStream(fileInput);
				database = (TreeMap<AssetEntity, ArrayList<PriceEntity>>) objectInput
						.readObject();
				vnIndexAsset = (AssetEntity) objectInput.readObject();
				vnIndex = (ArrayList<PriceEntity>) objectInput.readObject();
				objectInput.close();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}

		}

	}

}
